using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;

namespace Petra.Gis.DataServices.SHP
{
	/// <summary>
	/// Class that allows records in a dbase file to be enumerated.
	/// </summary>
	public partial class DbaseFileReader  : IEnumerable
	{


    internal static System.Globalization.NumberFormatInfo numberFormat_EnUS = new System.Globalization.CultureInfo("en-US", false).NumberFormat;

		
		private string _filename;
    private ArrayList recordData;
    private bool HeaderIsParsed;

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the DbaseFileReader class.
		/// </summary>
		protected DbaseFileReader(string filename) 
		{
			if (filename == null)
			{
				throw new ArgumentNullException(filename);
			}
			// check for the file existing here, otherwise we will not get an error
			//until we read the first record or read the header.
			if (!File.Exists(filename))
				throw new FileNotFoundException(String.Format("Could not find file \"{0}\"",filename));

      _filename = filename;

      ResetAll();
			
		}
		
		#endregion





    private void ResetAll()
    {
      FileStream fs = new FileStream(_filename, FileMode.Open, FileAccess.Read);

      br = new BinaryReader(fs);

      //Don't read the header if it's already parsed
      if (!HeaderIsParsed)
      {
        //ParseDbfHeader(_filename);
        _header = DbaseHeader.Read(br);
        HeaderIsParsed = true;
      }

      _isOpen = true;
      _currentRecord = 0;
    }





    //private System.Text.Encoding _Encoding;
    //private System.Text.Encoding _FileEncoding;

    public System.Text.Encoding TextEncoding
    {
      get { return _header.Encoding; }
      set 
      {
        _header.Encoding = value; 
      }
    }








		#region Implementation of IEnumerable

		/// <summary>
		/// Gets the object that allows iterating through the members of the collection.
		/// </summary>
		/// <returns>An object that implements the IEnumerator interface.</returns>
		public System.Collections.IEnumerator GetEnumerator()
		{
      if(_currentRecord != 0)
        this.ResetAll();

      while (Read())
        yield return recordData;

		}





    private DbaseHeader _header = null;


    public DbaseHeader DbfHeader
    {
      get { return _header; }
    }

    #endregion





    private bool _isOpen;





    /// <summary>
    /// Gets the feature at the specified Object ID
    /// </summary>
    /// <param name="oid"></param>
    /// <param name="table"></param>
    /// <returns></returns>
    internal ArrayList ReadRow(uint oid)
    {

      if (!_isOpen)
        throw (new ApplicationException("An attempt was made to read from a closed DBF file"));
      if (oid > _header.NumRecords)
        throw (new ArgumentException("Invalid DataRow requested at index " + oid.ToString()));

      br.BaseStream.Seek(_header.HeaderLength + oid * _header.RecordLength, 0);

      ArrayList result = new ArrayList();

      if (br.ReadChar() == '*') //is record marked deleted?
        return null;

      for (int i = 0; i < _header.Fields.Length; i++)
      {
        //DbaseField dbf = _header.Fields[i];
        result.Add(ReadDbfValue(_header.Fields[i]));
      }


      return result;
    }






    private object ReadDbfValue(DbaseField dbf)
    {
      switch (dbf.DataType.ToString())
      {
        case "System.String":
            return _header.Encoding.GetString(br.ReadBytes(dbf.Length)).Replace("\0", "").Trim();

        case "System.Double":
          string temp = System.Text.Encoding.UTF7.GetString(br.ReadBytes(dbf.Length)).Replace("\0", "").Trim();
          double dbl = 0;
          if (double.TryParse(temp, System.Globalization.NumberStyles.Float, numberFormat_EnUS, out dbl))
            return dbl;
          else
            return DBNull.Value;

        case "System.Int16":
          string temp16 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("\0", "").Trim();
          Int16 i16 = 0;
          if (Int16.TryParse(temp16, System.Globalization.NumberStyles.Float, numberFormat_EnUS, out i16))
            return i16;
          else
            return DBNull.Value;
        case "System.Int32":
          string temp32 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("\0", "").Trim();
          Int32 i32 = 0;
          if (Int32.TryParse(temp32, System.Globalization.NumberStyles.Float, numberFormat_EnUS, out i32))
            return i32;
          else
            return DBNull.Value;
        case "System.Int64":
          string temp64 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("\0", "").Trim();
          Int64 i64 = 0;
          if (Int64.TryParse(temp64, System.Globalization.NumberStyles.Float, numberFormat_EnUS, out i64))
            return i64;
          else
            return DBNull.Value;
        case "System.Single":
          string temp4 = System.Text.Encoding.UTF8.GetString((br.ReadBytes(dbf.Length)));
          float f = 0;
          if (float.TryParse(temp4, System.Globalization.NumberStyles.Float, numberFormat_EnUS, out f))
            return f;
          else
            return DBNull.Value;
        case "System.Boolean":
          char tempChar = br.ReadChar();
          return ((tempChar == 'T') || (tempChar == 't') || (tempChar == 'Y') || (tempChar == 'y'));
        case "System.DateTime":
          DateTime date;
          // Mono has not yet implemented DateTime.TryParseExact
#if !MONO
          if (DateTime.TryParseExact(System.Text.Encoding.UTF7.GetString((br.ReadBytes(8))),
            "yyyyMMdd", numberFormat_EnUS, System.Globalization.DateTimeStyles.None, out date))
            return date;
          else
            return DBNull.Value;
#else
					try 
					{
						return date = DateTime.ParseExact ( System.Text.Encoding.UTF7.GetString((br.ReadBytes(8))), 	
						"yyyyMMdd", numberFormat_EnUS, System.Globalization.DateTimeStyles.None );
					}
					catch ( Exception e )
					{
						return DBNull.Value;
					}
#endif
        default:
          throw (new NotSupportedException("Cannot parse DBase field '" + dbf.Name + "' of type '" + dbf.DataType.ToString() + "'"));
      }
    }





    public bool Read()
    {

      if (_currentRecord > _header.NumRecords)
      {
        br.Close();
        return false;
      }

      recordData = ReadRow(_currentRecord++);

      if (recordData == null)
        return false;
      else
        return true;


      
    }




  }
}
